package com.yoj.questionservice.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yoj.common.common.DeleteRequest;
import com.yoj.common.common.ResultCodeEnum;
import com.yoj.common.constant.CommonConstant;
import com.yoj.common.constant.RedisKeyConstant;
import com.yoj.common.exception.BusinessException;
import com.yoj.common.utils.RedisUtil;
import com.yoj.common.utils.SqlUtils;
import com.yoj.model.dto.question.*;
import com.yoj.model.dto.questionsubmit.QuestionSubmitAddRequest;
import com.yoj.model.entity.Question;
import com.yoj.model.entity.User;
import com.yoj.model.vo.QuestionVO;
import com.yoj.model.vo.UserVO;
import com.yoj.questionservice.mapper.QuestionMapper;
import com.yoj.questionservice.service.QuestionService;
import com.yoj.questionservice.service.QuestionSubmitService;
import com.yoj.serviceclient.service.user.UserFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author admin
 * @description 针对表【question(题目表)】的数据库操作Service实现
 * @createDate 2024-06-29 15:45:07
 */
@Slf4j
@Service
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question>
        implements QuestionService {

    @Resource
    private QuestionSubmitService questionSubmitService;

    @Resource
    private UserFeignClient userFeignClient;

    @Resource
    private RedisUtil redisUtil;

    /**
     * 校验题目是否合法
     */
    @Override
    public void validQuestion(Question question, boolean add) {
        if (question == null) {
            throw new BusinessException(ResultCodeEnum.PARAMS_ERROR);
        }
        String title = question.getTitle();
        String content = question.getContent();
        String tags = question.getTags();
        String answer = question.getAnswer();
        String judgeCase = question.getJudgeCase();
        String judgeConfig = question.getJudgeConfig();

        // 创建时，参数不能为空
        if (add && StringUtils.isAnyBlank(title, content, tags)) {
            throw new BusinessException(ResultCodeEnum.PARAMS_ERROR);
        }

        // 有参数则校验
        if (StringUtils.isNotBlank(title) && title.length() > 80) {
            throw new BusinessException(ResultCodeEnum.PARAMS_ERROR, "标题过长");
        }
        if (StringUtils.isNotBlank(content) && content.length() > 8192) {
            throw new BusinessException(ResultCodeEnum.PARAMS_ERROR, "内容过长");
        }
        if (StringUtils.isNotBlank(answer) && content.length() > 8192) {
            throw new BusinessException(ResultCodeEnum.PARAMS_ERROR, "答案过长");
        }
        if (StringUtils.isNotBlank(judgeCase) && content.length() > 8192) {
            throw new BusinessException(ResultCodeEnum.PARAMS_ERROR, "判题用例过长");
        }
        if (StringUtils.isNotBlank(judgeConfig) && content.length() > 8192) {
            throw new BusinessException(ResultCodeEnum.PARAMS_ERROR, "判题配置过长");
        }
    }

    /**
     * 获取查询包装类
     * 用户根据哪些字段查询，根据前端传来的请求对象，得到 mybatis 框架支持的查询 QueryWrapper 类
     */
    @Override
    public QueryWrapper<Question> getQueryWrapper(QuestionQueryRequest questionQueryRequest) {
        QueryWrapper<Question> queryWrapper = new QueryWrapper<>();
        if (questionQueryRequest == null) {
            return queryWrapper;
        }
        Long id = questionQueryRequest.getId();
        String title = questionQueryRequest.getTitle();
        String content = questionQueryRequest.getContent();
        List<String> tags = questionQueryRequest.getTags();
        String answer = questionQueryRequest.getAnswer();
        Long userId = questionQueryRequest.getUserId();
        String sortField = questionQueryRequest.getSortField();
        String sortOrder = questionQueryRequest.getSortOrder();

        // 拼接查询条件
        queryWrapper.like(StringUtils.isNotBlank(title), "title", title);
        queryWrapper.like(StringUtils.isNotBlank(content), "content", content);
        queryWrapper.like(StringUtils.isNotBlank(answer), "content", answer);
        if (CollUtil.isNotEmpty(tags)) {
            for (String tag : tags) {
                queryWrapper.like("tags", "\"" + tag + "\"");
            }
        }
        queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id);
        queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId);
        queryWrapper.eq("isDelete", 0);
        queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
                sortField);
        return queryWrapper;
    }

    /**
     * 获取查询封装类
     * 根据问题得到问题的封装类，用来脱敏
     */
    @Override
    public QuestionVO getQuestionVO(Question question, HttpServletRequest request) {
        QuestionVO questionVO = QuestionVO.objToVo(question);
        // 1. 关联查询用户信息
        Long userId = question.getUserId();
        User user = null;
        if (userId != null && userId > 0) {
            user = userFeignClient.getUserById(userId);
        }
        UserVO userVO = userFeignClient.getUserVO(user);
        questionVO.setUserVO(userVO);

        return questionVO;
    }

    /**
     * 获取分页题目查询的封装类
     */
    @Override
    public Page<QuestionVO> getQuestionVOPage(Page<Question> questionPage, HttpServletRequest request) {
        List<Question> questionList = questionPage.getRecords();
        Page<QuestionVO> questionVOPage = new Page<>(questionPage.getCurrent(), questionPage.getSize(), questionPage.getTotal());
        if (CollUtil.isEmpty(questionList)) {
            return questionVOPage;
        }
        // 1. 关联查询用户信息
        Set<Long> userIdSet = questionList.stream().map(Question::getUserId).collect(Collectors.toSet());
        Map<Long, List<User>> userIdUserListMap = userFeignClient.listUserByIds(userIdSet).stream()
                .collect(Collectors.groupingBy(User::getId));

        // 填充信息
        List<QuestionVO> questionVOList = questionList.stream().map(question -> {
            QuestionVO questionVO = QuestionVO.objToVo(question);
            Long userId = question.getUserId();
            User user = null;
            if (userIdUserListMap.containsKey(userId)) {
                user = userIdUserListMap.get(userId).get(0);
            }
            //填充到User包装类
            questionVO.setUserVO(userFeignClient.getUserVO(user));
            return questionVO;
        }).collect(Collectors.toList());
        questionVOPage.setRecords(questionVOList);
        return questionVOPage;
    }

    /**
     * 分页查询题目列表
     */
    @Override
    public Page<QuestionVO> getQuestionVOPage(QuestionQueryRequest questionQueryRequest, HttpServletRequest request) {
        String questionViewKey = RedisKeyConstant.QUESTION_VIEW_KEY;
        // 先从缓存中查询
        Object questions = redisUtil.get(questionViewKey);
        // 缓存中没有，再从数据库中查询
        if (questions == null) {
            // 分页查询题目
            long current = questionQueryRequest.getCurrent();
            long size = questionQueryRequest.getPageSize();
            Page<Question> questionPage = this.page(new Page<>(current, size));
            // 将 Question 转为 QuestionVO
            Page<QuestionVO> questionVOPage = getQuestionVOPage(questionPage, request);
            if (questionVOPage != null) {
                redisUtil.set(questionViewKey, questionVOPage);
            } else {
                // 如果查询结果为空，缓存一个空对象，防止缓存穿透
                redisUtil.set(questionViewKey, "null", 5);
            }
            return questionVOPage;
        }
        return (Page<QuestionVO>) questions;
    }

    /**
     * 分页查询题目列表（管理员）
     */
    @Override
    public Page<Question> getQuestionPage(QuestionQueryRequest questionQueryRequest) {
        String questionManagerKey = RedisKeyConstant.QUESTION_MANAGER_KEY;
        // 先从缓存中查询
        Object questions = redisUtil.get(questionManagerKey);
        // 缓存中没有，再从数据库中查询
        if (questions == null) {
            // 分页查询题目
            long current = questionQueryRequest.getCurrent();
            long size = questionQueryRequest.getPageSize();
            Page<Question> questionPage = this.page(new Page<>(current, size));
            if (questionPage != null) {
                redisUtil.set(questionManagerKey, questionPage);
            } else {
                // 如果查询结果为空，缓存一个空对象，防止缓存穿透
                redisUtil.set(questionManagerKey, "null", 5);
            }
            return questionPage;
        }
        return (Page<Question>) questions;
    }

    /**
     * 新增题目
     */
    @Override
    public Long addQuestion(QuestionAddRequest questionAddRequest, HttpServletRequest request) {
        Question question = new Question();
        BeanUtils.copyProperties(questionAddRequest, question);
        // 标签
        List<String> tags = questionAddRequest.getTags();
        if (tags != null) {
            question.setTags(JSONUtil.toJsonStr(tags));
        }
        // 判题用例
        List<JudgeCase> judgeCase = questionAddRequest.getJudgeCase();
        if (judgeCase != null) {
            question.setJudgeCase(JSONUtil.toJsonStr(judgeCase));
        }
        // 判题配置
        JudgeConfig judgeConfig = questionAddRequest.getJudgeConfig();
        if (judgeConfig != null) {
            question.setJudgeConfig(JSONUtil.toJsonStr(judgeConfig));
        }
        // 检验参数
        validQuestion(question, true);
        // 创建用户
        User loginUser = userFeignClient.getLoginUser(request);
        question.setUserId(loginUser.getId());
        question.setFavourNum(0);
        question.setThumbNum(0);
        // 保存题目信息
        boolean result = this.save(question);
        if (!result) {
            throw new BusinessException(ResultCodeEnum.OPERATION_ERROR);
        }
        // 更新题目缓存
        redisUtil.delete(RedisKeyConstant.QUESTION_VIEW_KEY);
        redisUtil.delete(RedisKeyConstant.QUESTION_MANAGER_KEY);
        return question.getId();
    }

    /**
     * 删除题目
     */
    @Override
    public Boolean deleteQuestion(DeleteRequest deleteRequest, HttpServletRequest request) {
        User user = userFeignClient.getLoginUser(request);
        long id = deleteRequest.getId();
        // 判断是否存在
        Question oldQuestion = this.getById(id);
        if (oldQuestion == null) {
            throw new BusinessException(ResultCodeEnum.NOT_FOUND_ERROR);
        }
        // 仅本人或管理员可删除
        if (!oldQuestion.getUserId().equals(user.getId()) && !userFeignClient.isAdmin(user)) {
            throw new BusinessException(ResultCodeEnum.NO_AUTH_ERROR);
        }
        boolean removeById = this.removeById(id);
        // 更新题目缓存
        if (removeById) {
            redisUtil.delete(RedisKeyConstant.QUESTION_VIEW_KEY);
            redisUtil.delete(RedisKeyConstant.QUESTION_MANAGER_KEY);
        }
        return removeById;
    }

    /**
     * 修改题目（仅管理员）
     */
    public Boolean updateQuestion(QuestionUpdateRequest questionUpdateRequest) {
        Question question = new Question();
        BeanUtils.copyProperties(questionUpdateRequest, question);
        // 标签
        List<String> tags = questionUpdateRequest.getTags();
        if (tags != null) {
            question.setTags(JSONUtil.toJsonStr(tags));
        }
        // 判题用例
        List<JudgeCase> judgeCase = questionUpdateRequest.getJudgeCase();
        if (judgeCase != null) {
            question.setJudgeCase(JSONUtil.toJsonStr(judgeCase));
        }
        // 判题配置
        JudgeConfig judgeConfig = questionUpdateRequest.getJudgeConfig();
        if (judgeConfig != null) {
            question.setJudgeConfig(JSONUtil.toJsonStr(judgeConfig));
        }
        // 参数校验
        validQuestion(question, false);
        long questionId = questionUpdateRequest.getId();
        // 判断该题目是否存在
        Question oldQuestion = this.getById(questionId);
        if (oldQuestion == null) {
            throw new BusinessException(ResultCodeEnum.NOT_FOUND_ERROR);
        }
        // 修改题目
        boolean updateById = this.updateById(question);
        // 更新题目缓存
        if (updateById) {
            redisUtil.delete(RedisKeyConstant.QUESTION_VIEW_KEY);
            redisUtil.delete(RedisKeyConstant.QUESTION_MANAGER_KEY);
        }
        return updateById;
    }

    /**
     * 根据 id 获取题目
     */
    @Override
    public Question getQuestionById(long questionId, HttpServletRequest request) {
        // 根据 id 查询题目
        Question question = this.getById(questionId);
        if (question == null) {
            throw new BusinessException(ResultCodeEnum.NOT_FOUND_ERROR);
        }
        User loginUser = userFeignClient.getLoginUser(request);
        // 不是本人或者管理员，不能直接获取信息
        if (!question.getUserId().equals(loginUser.getId()) && !userFeignClient.isAdmin(loginUser)) {
            throw new BusinessException(ResultCodeEnum.NO_AUTH_ERROR);
        }
        return question;
    }

    /**
     * 根据 id 获取题目视图
     */
    @Override
    public QuestionVO getQuestionVOById(long questionId, HttpServletRequest request) {
        // 根据 id 查询题目
        Question question = this.getById(questionId);
        if (question == null) {
            throw new BusinessException(ResultCodeEnum.NOT_FOUND_ERROR);
        }
        QuestionVO questionVO = getQuestionVO(question, request);
        return questionVO;
    }

    /**
     * 分页查询当前用户创建的题目列表
     */
    @Override
    public Page<QuestionVO> getUserQuestionVOPage(QuestionQueryRequest questionQueryRequest, HttpServletRequest request) {
        User loginUser = userFeignClient.getLoginUser(request);
        // 分页查询题目列表
        Page<Question> questionPage = this.page(new Page<>(questionQueryRequest.getCurrent(), questionQueryRequest.getPageSize()),
                new LambdaQueryWrapper<Question>()
                        .eq(Question::getUserId, loginUser.getId()));
        // 转换为题目包装类
        Page<QuestionVO> questionVOPage = getQuestionVOPage(questionPage, request);
        return questionVOPage;
    }

    /**
     * 提交题目
     */
    @Override
    public Long submitQuestion(QuestionSubmitAddRequest questionSubmitAddRequest, HttpServletRequest request) {
        final User loginUser = userFeignClient.getLoginUser(request);
        log.info("【题目提交成功，开始判题】，用户：{} ，题目提交 id：{}", questionSubmitAddRequest.getQuestionId(), loginUser.getId());
        Long questionSubmitId = questionSubmitService.doQuestionSubmit(questionSubmitAddRequest, loginUser);
        return questionSubmitId;
    }
}